﻿using DGZImp.Core.Extensions;
using DGZImp.Core.Services;
using DGZImp.MdsProject.Devices;
using DGZImp.MdsProject.Devices.Base;
using DGZImp.MdsProject.Devices.Helper;
using DGZImp.MdsProject.Devices.Models;
using Newtonsoft.Json;
using Nomad.Iot;
using Nomad.Iot.Core;
using Nomad.Iot.Profinet.Siemens;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DGZImp.MdsProject.Base
{
    public class PlcDevice<TData> : BaseDevice where TData : class
    {
        public List<PlcDataMapItem> DataMap { get; set; }

        public TData Data { get; set; }

        public delegate void DeviceDataChange(object sender);
        public event DeviceDataChange OnDataChange;

        public PlcDevice(string id, string name, IIotServe serve, List<PlcDataMapItem> maps) : base(id, name, serve)
        {
            DataMap = maps;
            serve.OnPlcDataReceive += DataReceiveHandler;
            Data = Activator.CreateInstance<TData>();
        }

        public void DataReceiveHandler(object sender, ConcurrentDictionary<string, byte[]> datas)
        {
            var data = ConvertFromPlcData<TData>(datas, DataMap, ((PlcServe)Serve).GetPlcByteTransform());
            Data = data;
            OnDataChange?.Invoke(this);
        }

        public T ConvertFromPlcData<T>(ConcurrentDictionary<string, byte[]> datas, List<PlcDataMapItem> map, ByteTransformBase byteTransform) where T : class
        {
            // 先把map按照dbblock分组，再根据分组key去datas中获取到分组对应的 bytes 数组
            var mapGroup = map.Where(x => x.Readable).GroupBy(x => x.DbBlock).ToList();
            var resVals = new Dictionary<string, object>();
            foreach (var item in mapGroup)
            {
                byte[] data = null;
                datas.TryGetValue(item.Key, out data);
                if (data == null) continue;
                foreach (var mapItem in item)
                {
                    try
                    {
                        object val = null;
                        switch (mapItem.DataType)
                        {
                            case DataTypeEnum.Byte:
                                val = byteTransform.TransByte(data, mapItem.Offset);
                                break;
                            case DataTypeEnum.Int16:
                                val = byteTransform.TransInt16(data, mapItem.Offset);
                                break;
                            case DataTypeEnum.Int32:
                                val = byteTransform.TransInt32(data, mapItem.Offset);
                                break;
                            case DataTypeEnum.Int64:
                                val = byteTransform.TransInt64(data, mapItem.Offset);
                                break;
                            case DataTypeEnum.UInt16:
                                val = byteTransform.TransUInt16(data, mapItem.Offset);
                                break;
                            case DataTypeEnum.UInt32:
                                val = byteTransform.TransUInt32(data, mapItem.Offset);
                                break;
                            case DataTypeEnum.UInt64:
                                val = byteTransform.TransUInt64(data, mapItem.Offset);
                                break;
                            case DataTypeEnum.Float:
                                val = Math.Round(byteTransform.TransSingle(data, mapItem.Offset), 2);
                                break;
                            case DataTypeEnum.Double:
                                val = Math.Round(byteTransform.TransDouble(data, mapItem.Offset), 2);
                                break;
                            case DataTypeEnum.String:
                                val = byteTransform.TransString(data, mapItem.Offset, mapItem.Length, Encoding.ASCII);
                                break;
                            default:
                                break;
                        }
                        resVals.Add(mapItem.PropName, val);
                    }
                    catch (Exception)
                    {
                        SysLogger.LogError($"[{Id}-{Name}]转换数据失败，属性：{mapItem.PropName}");
                        throw;
                    }
                }
            }

            // 创建 T 的实例
            var t = Activator.CreateInstance<T>();
            var properties = t.GetType().GetProperties().ToDictionary(prop => prop.Name, prop => prop);
            // 遍历 resVals 中的键值对
            foreach (var i in resVals)
            {
                // 检查键是否存在
                if (!properties.TryGetValue(i.Key, out var prop)) continue;
                // 将值赋给 T 的属性
                prop.SetValue(t, i.Value);
            }

            // 如果数据中有 RefreshTime 属性，则设置其值为当前时间
            if (properties.TryGetValue("RefreshTime", out var refreshTimeProp))
            {
                refreshTimeProp.SetValue(t, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
            }

            return t;
        }

        public async Task<OperateResult> WriteDataToPlc(List<PlcDataWriteItem> datas)
        {
            var res = new OperateResult() { IsSuccess = false };
            if (datas.Count == 0)
            {
                res.Message = $"[{Id}-{Name}]需写入数据为空";
                return res;
            }
            var dataArr = new List<PlcWriteItem>();
            foreach (var x in datas)
            {
                var item = DataMap.Where(j => j.PropName == x.PropName).FirstOrDefault();
                if (item == null)
                {
                    res.Message = $"[{Id}-{Name}]需写入属性[{x.PropName}]不存在";
                    return res;
                }
                if (!item.Writable)
                {
                    res.Message = $"[{Id}-{Name}]需写入属性[{x.PropName}]不可写";
                    return res;
                }
                if (item.WriteAddress.IsNullOrEmpty())
                {
                    res.Message = $"[{Id}-{Name}]需写入属性[{x.PropName}]未配置写入地址";
                    return res;
                }
                dataArr.Add(new PlcWriteItem()
                {
                    Address = item.WriteAddress,
                    DataType = item.DataType,
                    Value = ConvertTools.ConvertValue(item.DataType, x.Value),
                });
            }
            foreach (var x in dataArr)
            {
                res = await ((PlcServe)Serve).WriteDataAsync(x);
                if (res.IsSuccess)
                {
                    continue;
                }
                else
                {
                    return res;
                }
            }
            return res;
        }

    }

}


